﻿using System;
using System.Collections.Generic;
using System.Globalization;
using JTLeigh.Commerce.Spencer.Core.Common;
using JTLeigh.Commerce.Spencer.Features.ProductFeedback.Common.Entities;

namespace JTLeigh.Commerce.Spencer.Features.ProductFeedback.Business.Caches
{

	/// <summary>
	/// An in-memory <see cref="IProductFeedbackMetadataCache"/> implementation that caches product 
	/// feedback metadata in memory without expiration, scavenging, etc.
	/// </summary>
	/// <remarks>
	/// <para>
	/// This cache should not be used in production unless you are prepared to restart the service 
	/// host whenever product feedback metadata is updated.  This cache should not be used in 
	/// multi-server environments.
	/// </para>
	/// </remarks>
	public class InMemoryProductFeedbackMetadataCache : IProductFeedbackMetadataCache
	{

		#region Fields

		private Dictionary<Guid, ProductFeedbackMetadata> _metadata;
        private Dictionary<string, ProductFeedbackBadgeCategoryRelations> _productFeedbackBadgeCategoryRelations;
        private Dictionary<string, ProductFeedbackBadgeProductRelations> _productFeedbackBadgeProductRelations;
        private Dictionary<string, ProductRatingAttributeCategoryRelations> _productRatingAttributeCategoryRelations;
		private Dictionary<string, ProductRatingAttributeProductRelations> _productRatingAttributeProductRelations;
		private Dictionary<string, ProductRecommendationAttributeCategoryRelations> _productRecommendationAttributeCategoryRelations;
		private Dictionary<string, ProductRecommendationAttributeProductRelations> _productRecommendationAttributeProductRelations;
		private Dictionary<string, ProductReviewAttributeCategoryRelations> _productReviewAttributeCategoryRelations;
		private Dictionary<string, ProductReviewAttributeProductRelations> _productReviewAttributeProductRelations;

		#endregion // Fields

		#region Constructors

		/// <summary>
		/// Initializes a new instance of the <see cref="InMemoryProductFeedbackMetadataCache"/> 
		/// class.
		/// </summary>
		public InMemoryProductFeedbackMetadataCache()
		{
			_metadata = new Dictionary<Guid, ProductFeedbackMetadata>();
            _productFeedbackBadgeCategoryRelations = new Dictionary<string, ProductFeedbackBadgeCategoryRelations>();
            _productFeedbackBadgeProductRelations = new Dictionary<string, ProductFeedbackBadgeProductRelations>();
            _productRatingAttributeCategoryRelations = new Dictionary<string, ProductRatingAttributeCategoryRelations>();
            _productRatingAttributeProductRelations = new Dictionary<string, ProductRatingAttributeProductRelations>();
            _productRecommendationAttributeCategoryRelations = new Dictionary<string, ProductRecommendationAttributeCategoryRelations>();
			_productRecommendationAttributeProductRelations = new Dictionary<string, ProductRecommendationAttributeProductRelations>();
			_productReviewAttributeCategoryRelations = new Dictionary<string, ProductReviewAttributeCategoryRelations>();
			_productReviewAttributeProductRelations = new Dictionary<string, ProductReviewAttributeProductRelations>();
		}

		#endregion // Constructors

		#region IProductFeedbackMetadataCache Members

		/// <summary>
		/// Gets the product feedback metadata from the cache.
		/// </summary>
		/// <param name="tenantId">The tenant identifier <see cref="Guid"/> of the tenant whose 
		/// product feedback metadata is to be retrieved.</param>
		/// <returns>The <see cref="ProductFeedbackMetadata"/> object from the cache, or <see langword="null" /> 
		/// if the product feedback metadata has not been cached.</returns>
		public ProductFeedbackMetadata GetProductFeedbackMetadata(Guid tenantId)
		{
			ProductFeedbackMetadata metadata;

			_metadata.TryGetValue(tenantId, out metadata);
			return metadata;
		}

		/// <summary>
		/// Sets the product feedback metadata in the cache.
		/// </summary>
		/// <param name="tenantId">The tenant identifier <see cref="Guid"/> of the tenant whose 
		/// product feedback metadata is to be stored.</param>
		/// <param name="metadata">The <see cref="ProductFeedbackMetadata"/> object to store.</param>
		public void SetProductFeedbackMetadata(Guid tenantId, ProductFeedbackMetadata metadata)
		{
			Safety.AssertArgumentNotNull("metadata", metadata);

			_metadata[tenantId] = metadata;
		}

        /// <summary>
        /// Gets the product feedback badge - category relations for the specified category from 
        /// the cache.
        /// </summary>
        /// <param name="tenantId">The tenant identifier <see cref="Guid"/> of the tenant whose
        /// product feedback badge - category relations are to be retrieved.</param>
        /// <param name="catalogId">The catalog identifier <see cref="String"/> of the catalog
        /// that the category whose product feedback badge - category relations are to be retrieved
        /// is in.</param>
        /// <param name="categoryId">The category identifier <see cref="String"/> of the category
        /// whose product feedback badge - category relations are to be retrieved.</param>
        /// <returns>The <see cref="ProductFeedbackBadgeCategoryRelations"/> object for the 
        /// category from the cache, or <see langword="null" /> if product feedback badge - 
        /// category relations for the category have not been cached.</returns>
        public ProductFeedbackBadgeCategoryRelations GetProductFeedbackBadgeCategoryRelations(Guid tenantId, string catalogId, string categoryId)
        {
            string key;
            ProductFeedbackBadgeCategoryRelations relations;

            Safety.AssertArgumentNotNull("catalogId", catalogId);
            Safety.AssertArgumentNotNull("categoryId", categoryId);

            key = ComputeCategoryKey(tenantId, catalogId, categoryId);
            _productFeedbackBadgeCategoryRelations.TryGetValue(key, out relations);
            return relations;
        }

        /// <summary>
        /// Sets the product feedback badge - category relations for the specified category in 
        /// the cache.
        /// </summary>
        /// <param name="tenantId">The tenant identifier <see cref="Guid"/> of the tenant whose
        /// product feedback badge - category relations are to be stored.</param>
        /// <param name="catalogId">The catalog identifier <see cref="String"/> of the catalog
        /// whose product feedback badge - category relations are to be stored.</param>
        /// <param name="categoryId">The category identifier <see cref="String"/> of the category
        /// whose product feedback badge - category relations are to be stored.</param>
        /// <param name="relations">The <see cref="SetProductFeedbackBadgeCategoryRelations"/> 
        /// object to store.</param>
        public void SetProductFeedbackBadgeCategoryRelations(Guid tenantId, string catalogId, string categoryId, ProductFeedbackBadgeCategoryRelations relations)
        {
            string key;

            Safety.AssertArgumentNotNull("catalogId", catalogId);
            Safety.AssertArgumentNotNull("categoryId", categoryId);
            Safety.AssertArgumentNotNull("relations", relations);

            key = ComputeCategoryKey(tenantId, catalogId, categoryId);
            _productFeedbackBadgeCategoryRelations[key] = relations;
        }

        /// <summary>
        /// Gets the product feedback badge - product relations for the specified product from 
        /// the cache.
        /// </summary>
        /// <param name="tenantId">The tenant identifier <see cref="Guid"/> of the tenant whose
        /// product feedback badge - product relations are to be retrieved.</param>
        /// <param name="catalogId">The catalog identifier <see cref="String"/> of the catalog
        /// that the product whose product feedback badge - product relations are to be retrieved
        /// is in.</param>
        /// <param name="productId">The product identifier <see cref="String"/> of the product
        /// whose product feedback badge - product relations are to be retrieved.</param>
        /// <returns>The <see cref="ProductFeedbackBadgeProductRelations"/> object for the 
        /// product from the cache, or <see langword="null" /> if product feedback badge - 
        /// product relations for the product have not been cached.</returns>
        public ProductFeedbackBadgeProductRelations GetProductFeedbackBadgeProductRelations(Guid tenantId, string catalogId, string productId)
        {
            string key;
            ProductFeedbackBadgeProductRelations relations;

            Safety.AssertArgumentNotNull("catalogId", catalogId);
            Safety.AssertArgumentNotNull("productId", productId);

            key = ComputeProductKey(tenantId, catalogId, productId);
            _productFeedbackBadgeProductRelations.TryGetValue(key, out relations);
            return relations;
        }

        /// <summary>
        /// Sets the product feedback badge - product relations for the specified product in the
        /// cache.
        /// </summary>
        /// <param name="tenantId">The tenant identifier <see cref="Guid"/> of the tenant whose
        /// product feedback badge - product relations are to be stored.</param>
        /// <param name="catalogId">The catalog identifier <see cref="String"/> of the catalog
        /// whose product feedback badge - product relations are to be stored.</param>
        /// <param name="productId">The product identifier <see cref="String"/> of the product
        /// whose product feedback badge - product relations are to be stored.</param>
        /// <param name="relations">The <see cref="SetProductFeedbackBadgeProductRelations"/> 
        /// object to store.</param>
        public void SetProductFeedbackBadgeProductRelations(Guid tenantId, string catalogId, string productId, ProductFeedbackBadgeProductRelations relations)
        {
            string key;

            Safety.AssertArgumentNotNull("catalogId", catalogId);
            Safety.AssertArgumentNotNull("productId", productId);
            Safety.AssertArgumentNotNull("relations", relations);

            key = ComputeProductKey(tenantId, catalogId, productId);
            _productFeedbackBadgeProductRelations[key] = relations;
        }

        /// <summary>
		/// Gets the product rating attribute - category relations for the specified category from 
		/// the cache.
		/// </summary>
		/// <param name="tenantId">The tenant identifier <see cref="Guid"/> of the tenant whose
		/// product rating attribute - category relations are to be retrieved.</param>
        /// <param name="catalogId">The catalog identifier <see cref="String"/> of the catalog
        /// that the category whose product rating attribute - category relations are to be 
        /// retrieved is in.</param>
        /// <param name="categoryId">The category identifier <see cref="String"/> of the category
		/// whose product rating attribute - category relations are to be retrieved.</param>
		/// <returns>The <see cref="ProductRatingAttributeCategoryRelations"/> object for the 
		/// category from the cache, or <see langword="null" /> if product rating attribute - 
		/// category relations for the category have not been cached.</returns>
		public ProductRatingAttributeCategoryRelations GetProductRatingAttributeCategoryRelations(Guid tenantId, string catalogId, string categoryId)
		{
			string key;
			ProductRatingAttributeCategoryRelations relations;

            Safety.AssertArgumentNotNull("catalogId", catalogId);
            Safety.AssertArgumentNotNull("categoryId", categoryId);

			key = ComputeCategoryKey(tenantId, catalogId, categoryId);
			_productRatingAttributeCategoryRelations.TryGetValue(key, out relations);
			return relations;
		}

		/// <summary>
		/// Sets the product rating attribute - category relations for the specified category in 
		/// the cache.
		/// </summary>
		/// <param name="tenantId">The tenant identifier <see cref="Guid"/> of the tenant whose
		/// product rating attribute - category relations are to be stored.</param>
        /// <param name="catalogId">The catalog identifier <see cref="String"/> of the catalog
        /// whose product rating attribute - category relations are to be stored.</param>
        /// <param name="categoryId">The category identifier <see cref="String"/> of the category
        /// whose product rating attribute - category relations are to be stored.</param>
        /// <param name="relations">The <see cref="SetProductRatingAttributeCategoryRelations"/> 
		/// object to store.</param>
		public void SetProductRatingAttributeCategoryRelations(Guid tenantId, string catalogId, string categoryId, ProductRatingAttributeCategoryRelations relations)
		{
			string key;

            Safety.AssertArgumentNotNull("catalogId", catalogId);
            Safety.AssertArgumentNotNull("categoryId", categoryId);
			Safety.AssertArgumentNotNull("relations", relations);

			key = ComputeCategoryKey(tenantId, catalogId, categoryId);
			_productRatingAttributeCategoryRelations[key] = relations;
		}

		/// <summary>
		/// Gets the product rating attribute - product relations for the specified product from 
		/// the cache.
		/// </summary>
		/// <param name="tenantId">The tenant identifier <see cref="Guid"/> of the tenant whose
		/// product rating attribute - product relations are to be retrieved.</param>
        /// <param name="catalogId">The catalog identifier <see cref="String"/> of the catalog
        /// that the product whose product rating attribute - product relations are to be 
        /// retrieved is in.</param>
        /// <param name="productId">The product identifier <see cref="String"/> of the product
		/// whose product rating attribute - product relations are to be retrieved.</param>
		/// <returns>The <see cref="ProductRatingAttributeProductRelations"/> object for the 
		/// product from the cache, or <see langword="null" /> if product rating attribute - 
		/// product relations for the product have not been cached.</returns>
		public ProductRatingAttributeProductRelations GetProductRatingAttributeProductRelations(Guid tenantId, string catalogId, string productId)
		{
			string key;
			ProductRatingAttributeProductRelations relations;

            Safety.AssertArgumentNotNull("catalogId", catalogId);
            Safety.AssertArgumentNotNull("productId", productId);

			key = ComputeProductKey(tenantId, catalogId, productId);
			_productRatingAttributeProductRelations.TryGetValue(key, out relations);
			return relations;
		}

		/// <summary>
		/// Sets the product rating attribute - product relations for the specified product in the
		/// cache.
		/// </summary>
		/// <param name="tenantId">The tenant identifier <see cref="Guid"/> of the tenant whose
		/// product rating attribute - product relations are to be stored.</param>
        /// <param name="catalogId">The catalog identifier <see cref="String"/> of the catalog
        /// whose product rating attribute - product relations are to be stored.</param>
        /// <param name="productId">The product identifier <see cref="String"/> of the product
		/// whose product rating attribute - product relations are to be stored.</param>
		/// <param name="relations">The <see cref="SetProductRatingAttributeProductRelations"/> 
		/// object to store.</param>
		public void SetProductRatingAttributeProductRelations(Guid tenantId, string catalogId, string productId, ProductRatingAttributeProductRelations relations)
		{
			string key;

            Safety.AssertArgumentNotNull("catalogId", catalogId);
            Safety.AssertArgumentNotNull("productId", productId);
			Safety.AssertArgumentNotNull("relations", relations);

			key = ComputeProductKey(tenantId, catalogId, productId);
			_productRatingAttributeProductRelations[key] = relations;
		}

		/// <summary>
		/// Gets the product recommendation attribute - category relations for the specified 
		/// category from the cache.
		/// </summary>
		/// <param name="tenantId">The tenant identifier <see cref="Guid"/> of the tenant whose
		/// product recommendation attribute - category relations are to be retrieved.</param>
        /// <param name="catalogId">The catalog identifier <see cref="String"/> of the catalog that
        /// the category whose product recommendation attribute - category relations are to be 
        /// retrieved is in.</param>
        /// <param name="categoryId">The category identifier <see cref="String"/> of the category
		/// whose product recommendation attribute - category relations are to be retrieved.</param>
		/// <returns>The <see cref="ProductRecommendationAttributeCategoryRelations"/> object for 
		/// the category from the cache, or <see langword="null" /> if product recommendation 
		/// attribute - category relations for the category have not been cached.</returns>
		public ProductRecommendationAttributeCategoryRelations GetProductRecommendationAttributeCategoryRelations(Guid tenantId, string catalogId, string categoryId)
		{
			string key;
			ProductRecommendationAttributeCategoryRelations relations;

            Safety.AssertArgumentNotNull("catalogId", catalogId);
            Safety.AssertArgumentNotNull("categoryId", categoryId);

			key = ComputeCategoryKey(tenantId, catalogId, categoryId);
			_productRecommendationAttributeCategoryRelations.TryGetValue(key, out relations);
			return relations;
		}

		/// <summary>
		/// Sets the product recommendation attribute - category relations for the specified 
		/// category in the cache.
		/// </summary>
		/// <param name="tenantId">The tenant identifier <see cref="Guid"/> of the tenant whose
		/// product recommendation attribute - category relations are to be stored.</param>
        /// <param name="catalogId">The catalog identifier <see cref="String"/> of the catalog 
        /// product recommendation attribute - category relations are to be stored.</param>
        /// <param name="categoryId">The category identifier <see cref="String"/> of the category
		/// whose product recommendation attribute - category relations are to be stored.</param>
		/// <param name="relations">The <see cref="SetProductRecommendationAttributeCategoryRelations"/> 
		/// object to store.</param>
		public void SetProductRecommendationAttributeCategoryRelations(Guid tenantId, string catalogId, string categoryId, ProductRecommendationAttributeCategoryRelations relations)
		{
			string key;

            Safety.AssertArgumentNotNull("catalogId", catalogId);
            Safety.AssertArgumentNotNull("categoryId", categoryId);
			Safety.AssertArgumentNotNull("relations", relations);

			key = ComputeCategoryKey(tenantId, catalogId, categoryId);
			_productRecommendationAttributeCategoryRelations[key] = relations;
		}

		/// <summary>
		/// Gets the product recommendation attribute - product relations for the specified product 
		/// from the cache.
		/// </summary>
		/// <param name="tenantId">The tenant identifier <see cref="Guid"/> of the tenant whose
		/// product recommendation attribute - product relations are to be retrieved.</param>
        /// <param name="catalogId">The catalog identifier <see cref="String"/> of the catalog
        /// that the product whose product recommendation attribute - product relations are to be 
        /// retrieved is in.</param>
        /// <param name="productId">The product identifier <see cref="String"/> of the product
		/// whose product recommendation attribute - product relations are to be retrieved.</param>
		/// <returns>The <see cref="ProductRecommendationAttributeProductRelations"/> object for 
		/// the product from the cache, or <see langword="null" /> if product recommendation 
		/// attribute - product relations for the product have not been cached.</returns>
		public ProductRecommendationAttributeProductRelations GetProductRecommendationAttributeProductRelations(Guid tenantId, string catalogId, string productId)
		{
			string key;

			ProductRecommendationAttributeProductRelations relations;

            Safety.AssertArgumentNotNull("catalogId", catalogId);
            Safety.AssertArgumentNotNull("productId", productId);

			key = ComputeProductKey(tenantId, catalogId, productId);
			_productRecommendationAttributeProductRelations.TryGetValue(key, out relations);
			return relations;
		}

		/// <summary>
		/// Sets the product recommendation attribute - product relations for the specified product 
		/// in the cache.
		/// </summary>
		/// <param name="tenantId">The tenant identifier <see cref="Guid"/> of the tenant whose
		/// product recommendation attribute - product relations are to be stored.</param>
        /// <param name="catalogId">The catalog identifier <see cref="String"/> of the catalog
        /// whose product recommendation attribute - product relations are to be stored.</param>
        /// <param name="productId">The product identifier <see cref="String"/> of the product 
		/// whose product recommendation attribute - product relations are to be stored.</param>
		/// <param name="relations">The <see cref="SetProductRecommendationAttributeProductRelations"/> 
		/// object to store.</param>
		public void SetProductRecommendationAttributeProductRelations(Guid tenantId, string catalogId, string productId, ProductRecommendationAttributeProductRelations relations)
		{
			string key;

            Safety.AssertArgumentNotNull("catalogId", catalogId);
            Safety.AssertArgumentNotNull("productId", productId);
			Safety.AssertArgumentNotNull("relations", relations);

			key = ComputeProductKey(tenantId, catalogId, productId);
			_productRecommendationAttributeProductRelations[key] = relations;
		}

		/// <summary>
		/// Gets the product review attribute - category relations for the specified category from 
		/// the cache.
		/// </summary>
		/// <param name="tenantId">The tenant identifier <see cref="Guid"/> of the tenant whose
		/// product review attribute - category relations are to be retrieved.</param>
        /// <param name="catalogId">The catalog identifier <see cref="String"/> of the catalog that
        /// the category whose product review attribute - category relations are to be retrieved
        /// is in.</param>
        /// <param name="categoryId">The category identifier <see cref="String"/> of the category
		/// whose product review attribute - category relations are to be retrieved.</param>
		/// <returns>The <see cref="ProductReviewAttributeCategoryRelations"/> object for the
		/// category from the cache, or <see langword="null" /> if product review attribute - 
		/// category relations for the category have not been cached.</returns>
		public ProductReviewAttributeCategoryRelations GetProductReviewAttributeCategoryRelations(Guid tenantId, string catalogId, string categoryId)
		{
			string key;
			ProductReviewAttributeCategoryRelations relations;

            Safety.AssertArgumentNotNull("catalogId", catalogId);
            Safety.AssertArgumentNotNull("categoryId", categoryId);

			key = ComputeCategoryKey(tenantId, catalogId, categoryId);
			_productReviewAttributeCategoryRelations.TryGetValue(key, out relations);
			return relations;
		}

		/// <summary>
		/// Sets the product review attribute - category relations for the specified category in 
		/// the cache.
		/// </summary>
		/// <param name="tenantId">The tenant identifier <see cref="Guid"/> of the tenant whose
		/// product review attribute - category relations are to be stored.</param>
        /// <param name="catalogId">The catalog identifier <see cref="String"/> of the catalog that
        /// the category whose product review attribute - category relations are to be stored is
        /// in.</param>
		/// <param name="categoryId">The category identifier <see cref="String"/> of the category
		/// whose product review attribute - category relations are to be stored.</param>
		/// <param name="relations">The <see cref="SetProductReviewAttributeCategoryRelations"/> 
		/// object to store.</param>
		public void SetProductReviewAttributeCategoryRelations(Guid tenantId, string catalogId, string categoryId, ProductReviewAttributeCategoryRelations relations)
		{
			string key;

            Safety.AssertArgumentNotNull("catalogId", catalogId);
            Safety.AssertArgumentNotNull("categoryId", categoryId);
			Safety.AssertArgumentNotNull("relations", relations);

			key = ComputeCategoryKey(tenantId, catalogId, categoryId);
			_productReviewAttributeCategoryRelations[key] = relations;
		}

		/// <summary>
		/// Gets the product review attribute - product relations for the specified product from 
		/// the cache.
		/// </summary>
		/// <param name="tenantId">The tenant identifier <see cref="Guid"/> of the tenant whose
		/// product review attribute - product relations are to be retrieved.</param>
        /// <param name="catalogId">The catalog identifier <see cref="String"/> of the catalog
        /// that the product whose product review attribute - product relations are to be retrieved
        /// is in.</param>
        /// <param name="productId">The product identifier <see cref="String"/> of the product 
		/// whose product review attribute - product relations are to be retrieved.</param>
		/// <returns>The <see cref="ProductReviewAttributeProductRelations"/> object for the 
		/// product from the cache, or <see langword="null" /> if product review attribute - 
		/// product relations for the product have not been cached.</returns>
		public ProductReviewAttributeProductRelations GetProductReviewAttributeProductRelations(Guid tenantId, string catalogId, string productId)
		{
			string key;
			ProductReviewAttributeProductRelations relations;

            Safety.AssertArgumentNotNull("catalogId", catalogId);
            Safety.AssertArgumentNotNull("productId", productId);

			key = ComputeProductKey(tenantId, catalogId, productId);
			_productReviewAttributeProductRelations.TryGetValue(key, out relations);
			return relations;
		}

		/// <summary>
		/// Sets the product review attribute - product relations for the specified product in the
		/// cache.
		/// </summary>
		/// <param name="tenantId">The tenant identifier <see cref="Guid"/> of the tenant whose
		/// product review attribute - product relations are to be stored.</param>
        /// <param name="catalogId">The catalog identifier <see cref="String"/> of the catalog
        /// whose product review attribute - product relations are to be stored.</param>
        /// <param name="productId">The product identifier <see cref="String"/> of the product
		/// whose product review attribute - product relations are to be stored.</param>
		/// <param name="relations">The <see cref="SetProductReviewAttributeProductRelations"/> 
		/// object to store.</param>
		public void SetProductReviewAttributeProductRelations(Guid tenantId, string catalogId, string productId, ProductReviewAttributeProductRelations relations)
		{
			string key;

            Safety.AssertArgumentNotNull("catalogId", catalogId);
            Safety.AssertArgumentNotNull("productId", productId);
			Safety.AssertArgumentNotNull("relations", relations);

			key = ComputeProductKey(tenantId, catalogId, productId);
			_productReviewAttributeProductRelations[key] = relations;
		}

		#endregion // IProductFeedbackMetadataCache

		#region Implementation

		private static string ComputeCategoryKey(Guid tenantId, string catalogId, string categoryId)
		{
            Safety.AssertArgumentNotNull("catalogId", catalogId);
            Safety.AssertArgumentNotNull("categoryId", categoryId);

			return String.Concat(tenantId.ToString("D", CultureInfo.InvariantCulture), ":", categoryId);
		}

		private static string ComputeProductKey(Guid tenantId, string catalogId, string productId)
		{
            Safety.AssertArgumentNotNull("catalogId", catalogId);
            Safety.AssertArgumentNotNull("productId", productId);

			return String.Concat(tenantId.ToString("D", CultureInfo.InvariantCulture), ":", productId);
		}

		#endregion // Implementation

	}

}
